﻿// #define DRLOG
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using MyUtils.Properties;

namespace MyUtils
{
	public static class DebugUtils
	{
		#region Logging

		[Conditional("DRLOG")]
		static void LogDr(string message)
		{
			Log("Debugger Reattaching: " + message);
		}

#if DRLOG
		static readonly string logFile = Path.Combine(Path.GetTempPath(), "DebuggerReattaching_" + DateTime.Now.Minute + ".log");
#endif

		[Conditional("DRLOG")]
		static void Log(string message)
		{
#if DRLOG
			message = "P" + Process.GetCurrentProcess().Id + " T" + Thread.CurrentThread.ManagedThreadId + " " + message;
			File.AppendAllText(logFile, message + Environment.NewLine);
#endif
		}

		#endregion

		static bool _alreadyCheckedOnThisApplicationDomain;
		static EventWaitHandle _shouldReattachDebugger;

		static readonly Mutex _debuggerReattachingLock = new Mutex(false, "814e126d-b286-4b5d-a721-65a867747014");
		const string _shouldReattachDebuggerEventWaitHandleName = "67a5cc7f-43da-4972-a640-e3b8d41015fa";

		static void AutoClosingNotififyDialog()
		{
			Process p = Process.GetCurrentProcess();

			// dialog thread
			Thread threadDialog = null;
			var eventDialogStarted = new ManualResetEvent(false);
			var eventDialogExited = new ManualResetEvent(false);
			var eventDialogForceClose = new ManualResetEvent(false);
			ThreadPool.QueueUserWorkItem(delegate
			{
				try
				{
					threadDialog = Thread.CurrentThread;
					eventDialogStarted.Set();
					AutoClosingMessageBox.ShowDialog(Resources.ReattachDebugger_Message.ArgI(p.ProcessName, p.Id), eventDialogForceClose);
				}
				finally
				{
					eventDialogExited.Set();
				}
			});

			Thread threadMonitor = null;
			var eventMonitorStarted = new ManualResetEvent(false);
			var eventMonitorDebuggerAttached = new ManualResetEvent(false);
			ThreadPool.QueueUserWorkItem(delegate
			{
				threadMonitor = Thread.CurrentThread;
				eventMonitorStarted.Set();
				while (true)
				{
					Thread.Sleep(100);
					if (Debugger.IsAttached)
					{
						eventMonitorDebuggerAttached.Set();
						break;
					}
				}
			});

			// waits for thread pool to achieve references to thread objects to allow abort it in future
			eventDialogStarted.WaitOne();
			eventMonitorStarted.WaitOne();

			WaitHandle.WaitAny(new[]
			{
				eventMonitorDebuggerAttached, eventDialogExited
			});

			try
			{
				eventDialogForceClose.Set();
				threadDialog.Abort();
				threadMonitor.Abort();
			}
			catch (Exception ex)
			{
				Trace.TraceError(ex.ToString());
			}
		}

		public static void DebuggerReattaching()
		{
			DebuggerReattaching("default");
		}

		public static void DebuggerReattaching(string instanceChainId)
		{
			try
			{
				_debuggerReattachingLock.WaitOne();

				LogDr("1: _alreadyCheckedOnThisApplicationDomain = " + _alreadyCheckedOnThisApplicationDomain);
				if (!_alreadyCheckedOnThisApplicationDomain)
				{
					_alreadyCheckedOnThisApplicationDomain = true;
					bool isNew;
					_shouldReattachDebugger = new EventWaitHandle(false, EventResetMode.ManualReset, _shouldReattachDebuggerEventWaitHandleName + "_" + instanceChainId, out isNew);
					LogDr("2: EventWaitHandle connected: isNew = " + isNew);
					if (isNew)
					{
						if (Debugger.IsAttached) // should mark EventWaitHandle signalled
						{
							LogDr("3: EventWaitHandle is new, debugger is attached, should set signal");
							_shouldReattachDebugger.Set();
						}
					}

					bool isSignaled = _shouldReattachDebugger.WaitOne(1, false);
					LogDr("4: isSignaled = " + isSignaled);

					LogDr("6: isNew = {0}, Debugger.IsAttached = {1}, isSignaled = {2}".ArgI(isNew, Debugger.IsAttached, isSignaled));

					if (!isNew && !Debugger.IsAttached && isSignaled)
					{
						LogDr("7: notify & wait");
						AutoClosingNotififyDialog();
					}
				}
			}

			finally
			{
				_debuggerReattachingLock.ReleaseMutex();
			}
		}

		/// <summary>
		/// Sleep until debugger is attached
		/// </summary>
		public static void WaitForDebugger()
		{
			Utils.WaitForEx(() => Debugger.IsAttached, int.MaxValue);
		}
	}
}